﻿// <copyright file="RenderEntity.cs" company="Axelerate Solutions S.A.">
// Copyright (c) Axelerate Solutions S.A.  All rights reserved.
// </copyright>

namespace Axelerate.Silverlight3D.Media.Media3D
{
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;

    /// <summary>
    /// File name: RenderEntity.cs
    /// Company: Axelerate Solutions S.A.
    /// Created By: Daniel Rojas
    /// Created Date: 10/06/2008 17:16:30 h.
    /// Description: Structure that contains the 2D Shape (Shape Class) and the Depth (double) of the shape on the scene.
    ///   <Modifications>
    ///   </Modifications>
    /// </summary>
    internal sealed class RenderEntity
    {
        #region Fields
        
        /// <summary>
        /// Visual element to show in the Viewport. Can be a polygon or any frameworkelement in the case of sprites
        /// </summary>
        private FrameworkElement visual;

        /// <summary>
        /// Catch a empty shape to be used as visual of the render entity by default.
        /// </summary>
        private Shape emptyBaseVisual;

        /// <summary>
        /// Distance of shape to the camera (to sort the reder of the faces)
        /// </summary>
        private double zIndex;

        /// <summary>
        /// Normal vector for this Render entity.
        /// </summary>
        private Vector3D normal;

        /// <summary>
        /// Render Entity pool to keep alive the instance of the render entities. Performance tuning.
        /// </summary>
        private RenderEntityPool renderEntityPool;

        /// <summary>
        /// Pointer to the mesh object.
        /// </summary>
        private MeshGeometry3D mesh;

        /// <summary>
        /// pointer to front material.
        /// </summary>
        private Material frontMaterial;

        /// <summary>
        /// Pointer to back material (can be null)
        /// </summary>
        private Material backMaterial;
                
        /// <summary>
        /// In case this render entity is a entity, this is the index of the vertex 0 for the traingle on the mesh.
        /// </summary>
        private int v0Index;

        /// <summary>
        /// In case this render entity is a entity, this is the index of the vertex 1 for the traingle on the mesh.
        /// </summary>
        private int v1Index;

        /// <summary>
        /// In case this render entity is a entity, this is the index of the vertex 2 for the traingle on the mesh.
        /// </summary>
        private int v2Index;

        /// <summary>
        /// Indicate if this render entity represent a poligon that is actually back facing the camera.
        /// </summary>
        private bool isBackFacing;

        #endregion

        #region Properties

        /// <summary>
        /// Visual element to show in the Viewport
        /// </summary>
        public FrameworkElement Visual
        {
            get
            {
                return visual;
            }
            internal set
            {
                visual = value;
            }
        }

        /// <summary>
        /// Distance of shape to the camera (to sort the reder of the faces)
        /// </summary>
        public double ZIndex
        {
            get
            {
                return this.zIndex;
            }
            set
            {
                this.zIndex = value;
            }
        }

        /// <summary>
        /// Internal variable to force change the rendering order. This is useful in case of have large objects and small objects in the same viewport and need to fix the rendering orderer.
        /// </summary>
        internal double ZIndexOverride
        {
            get;
            set;
        }

        /// <summary>
        /// Normal vector for this Render entity.
        /// </summary>
        public Vector3D Normal
        {
            get
            {
                return this.normal;
            }
        }

        /// <summary>
        /// Centroid of the entity or the initial position of the sprite
        /// </summary>
        public Point3D Centroid
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the vertex 0 Point3D of the mesh
        /// </summary>
        public Point3D P0
        {
            get 
            {
                if (this.mesh != null)
                {
                    return this.mesh.Positions[this.v0Index];
                }
                else
                {
                    return new Point3D();
                }
            }
        }

        /// <summary>
        /// Gets the vertex 1 Point3D of the mesh
        /// </summary>
        public Point3D P1
        {
            get { return this.mesh.Positions[this.v1Index]; }
        }

        /// <summary>
        /// Gets the vertex 2 Point3D of the mesh
        /// </summary>
        public Point3D P2
        {
            get { return this.mesh.Positions[this.v2Index]; }
        }
        
        /// <summary>
        /// Gets or sets a value indicating wheter the poligon represented by this render entity is back facing the camera.
        /// </summary>
        public bool IsBackFacing
        {
            get
            {
                return isBackFacing;
            }
            set
            {
                isBackFacing = value;
            }
        }

        /// <summary>
        /// Gets or sets the FrontMaterial for this render entity.
        /// </summary>
        internal Material FrontMaterial
        {
            get { return this.frontMaterial; }
            set { this.frontMaterial = value; }
        }

        /// <summary>
        /// Gest or sets the BackMaterial for this render entity.
        /// </summary>
        internal Material BackMaterial
        {
            get { return this.backMaterial; }
            set { this.backMaterial = value; }
        }

        #endregion

        #region Constructor

        
        
        /// <summary>
        /// Create a new instance of the render entity. This method is used to be called by the RenderEntityPool
        /// </summary>
        internal RenderEntity(RenderEntityPool renderEntityPool)
        {
            this.renderEntityPool = renderEntityPool;

            // This is what will actually be rendered onto the screen
            Polygon newvisual = new Polygon();
            newvisual.Points = new System.Windows.Media.PointCollection();
            newvisual.Points.Add(new Point(0, 0));
            newvisual.Points.Add(new Point(0, 0));
            newvisual.Points.Add(new Point(0, 0));
            newvisual.UseLayoutRounding = true;

            this.visual = newvisual;
            this.emptyBaseVisual = newvisual;
            
            // this.visual.IsHitTestVisible = false;
        }
        #endregion

        /// <summary>
        /// Compare two RenderEntity and return 0 if a == b, 1 if a &lt; b and -1 if a &gt; b
        /// </summary>
        /// <param name="a">First RenderEntity to compare</param>
        /// <param name="b">Second RenderEntity to compare</param>
        /// <returns>0 if a == b, 1 if a &lt; b and -1 if a &gt; b</returns>
        public static int CompareDepth(RenderEntity a, RenderEntity b)
        {
             // ((a.ZIndexOverride != b.ZIndexOverride) ? a.ZIndex.CompareTo(b.ZIndexOverride) : 
            return (a.ZIndexOverride != b.ZIndexOverride) ? -1 * a.ZIndex.CompareTo(b.ZIndexOverride) : (a.ZIndex < b.ZIndex ? 1 : (a.ZIndex > b.ZIndex) ? -1 : 0);
        }

        /// <summary>
        /// DO NOT CALL THIS DIRECTLY - must create entities through
        /// the TrianglePool class
        /// </summary>
        /// <param name="mesh"></param>
        /// <param name="v0Index"></param>
        /// <param name="v1Index"></param>
        /// <param name="v2Index"></param>
        /// <param name="frontMaterial"></param>
        /// <param name="backMaterial"></param>
        internal void Initialize(MeshGeometry3D mesh,
                                 int v0Index,
                                 int v1Index,
                                 int v2Index,
                                 Material frontMaterial,
                                 Material backMaterial)
        {
            this.Clear();

            this.mesh = mesh;

            if (this.mesh == null)
            {
                return;
            }
            this.visual = this.emptyBaseVisual;

            this.v0Index = v0Index;
            this.v1Index = v1Index;
            this.v2Index = v2Index;

            Vector3D v0 = (Vector3D)this.P0;
            Vector3D v1 = (Vector3D)this.P1;
            Vector3D v2 = (Vector3D)this.P2;

            // Centroid of the face.
            this.Centroid = (Point3D)((v0 + v1 + v2) / 3.0);

            // Calculate Face normal
            this.normal = Vector3D.CrossProduct(v1 - v0,
                                                v2 - v0);
            this.normal.Normalize();

            if (frontMaterial != null)
            {
                this.FrontMaterial = frontMaterial; 
            }

            if (backMaterial != null)
            {
                this.BackMaterial = backMaterial; 
            }
        }

        /// <summary>
        /// Clear the render entity widhtout destroyit to allow use the same instance for other element. 
        /// </summary>
        internal void Clear()
        {
            this.mesh = null;
            this.ZIndexOverride = 0;
            
            this.visual = this.emptyBaseVisual; 
            (this.Visual as Shape).Fill = null;
            
            this.FrontMaterial = null;
            this.BackMaterial = null;
        }

        /// <summary>
        /// Gets a value indicating wheter this rendery entity is a entity. (can be a sprite)
        /// </summary>
        public bool IsTriangle
        {
            get
            {
                return (this.Visual is Shape && this.mesh != null);
            }
        }

        /// <summary>
        /// Updates the polgon positions on the screen. Triangle only.
        /// </summary>
        /// <param name="x0">first x position</param>
        /// <param name="y0">first y position</param>
        /// <param name="x1">second x position</param>
        /// <param name="y1">second y position</param>
        /// <param name="x2">tird x position</param>
        /// <param name="y2">tird y position</param>
        internal void SetTrianglePoints(Point sp0,
                                      Point sp1,
                                      Point sp2)
        {
            
            if (this.IsTriangle)
            {
                Polygon myVisual = this.visual as Polygon;
                if (myVisual != null)
                {
                    myVisual.Points.Clear();
                    myVisual.Points.Add(new Point(sp0.X, sp0.Y));
                    myVisual.Points.Add(new Point(sp1.X, sp1.Y));
                    myVisual.Points.Add(new Point(sp2.X, sp2.Y));
                }
            }
            if (this.mesh != null && this.mesh.TextureCoordinates != null && this.mesh.TextureCoordinates.Count > 0)
            {
                // TODO: calcuate the matrix position for render the correct texturing. If the material is a Texture
            }
        }
    }
}

